﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using AnaxibiaStudios.Smug;
using System.Reflection;
using AnaxibiaStudios.Smug.Advanced;
using System.IO;
using System.Linq.Expressions;
using AnaxibiaStudios.Smug.Internal;

namespace SmugTest
{
  internal class NonNestedInternalClass { }
  class NonNestedPrivateClass { }
  public class NonNestedPublicClass { }

  [TestClass]
  public class BuildTest
  {
    public abstract class InternalAbstractMethod { internal abstract void Foo(); }
    public abstract class InternalAbstractProperty { internal abstract int Foo { get; } }
    public abstract class InternalAbstractEvent { internal abstract event Action MEvent; }
    public abstract class ProtectedInternalAbstractMethod { protected internal abstract void Foo(); }
    public abstract class ProtectedInternalAbstractProperty { protected internal abstract int Foo { get; } }
    public abstract class ProtectedInternalAbstractEvent { protected internal abstract event Action MEvent;}
    public class InternalVirtualMethod { internal virtual void Foo() { } }
    public class InternalVirtualProperty { internal virtual int Foo { get; set; } }
    public class InternalVirtualEvent { internal virtual event Action MEvent;}
    public class HasNoInternalsExampleMethod { protected virtual void Foo() { } }
    public class HasNoInternalsExampleProperty { protected virtual int Foo { get; set; } }
    public class HasNoInternalsExampleEvent { protected virtual event Action MEvent;}

    [TestMethod]
    public void HasInternalsTest()
    {
      /*
    * internal
    * protected
    * virtual/abstract
    * 
    * we only block abstract AND internal
    * ok: protected internal abstract
    * ok: internal virtual
    * ok: anything without internal
    * 
    */

      HasInternalsHelper<InternalAbstractMethod>(false);
      HasInternalsHelper<InternalAbstractProperty>(false);
      HasInternalsHelper<InternalAbstractEvent>(false);
      HasInternalsHelper<ProtectedInternalAbstractMethod>(true, 0);
      HasInternalsHelper<ProtectedInternalAbstractProperty>(true, 0);
      HasInternalsHelper<ProtectedInternalAbstractEvent>(true, 0);
      HasInternalsHelper<InternalVirtualMethod>(true);
      HasInternalsHelper<InternalVirtualProperty>(true);
      HasInternalsHelper<InternalVirtualEvent>(true);
      HasInternalsHelper<HasNoInternalsExampleMethod>(true, 0);
      HasInternalsHelper<HasNoInternalsExampleProperty>(true, 0);
      HasInternalsHelper<HasNoInternalsExampleEvent>(true, 0);
    }

    private void HasInternalsHelper<T>(bool passes, int internalCount = 1)
    {
      Assert.AreEqual(!passes, (new SourceBuilder(typeof(T)).HasAbstractInternals()), string.Format("{0} has internals", typeof(T).Name));
      Assert.AreEqual(internalCount, new SourceBuilder(typeof(T)).MemberCollection.Count(ex => ex.IsInternal), string.Format("internal member count for {0}", typeof(T).Name));

      if (passes)
      {
        var inst = Create.Proxy<T>();
      }
      else
      {
        try
        {
          var wne = Create.Proxy<T>();
          Assert.Fail(string.Format("proxy create failure expected for {0}", typeof(T).Name));
        }
        catch (SmugHasAbstractInternalsException) { }
      }
    }

    internal class NestedInternalClass { }
    protected internal class NestedProtectedInternalClass { }
    class NestedPrivateClass { }
    public class NestedPublicClass { }
    protected class NestedProtectedClass { }

    [TestMethod]
    public void InternalClassTest()
    {
      InternalClassTestHelper<SmugTest.NonNestedInternalClass>(true);
      InternalClassTestHelper<SmugTest.NonNestedPrivateClass>(true);
      InternalClassTestHelper<SmugTest.NonNestedPublicClass>(false);

      InternalClassTestHelper<SmugTest.BuildTest.NestedInternalClass>(true);
      InternalClassTestHelper<SmugTest.BuildTest.NestedProtectedInternalClass>(true);
      InternalClassTestHelper<SmugTest.BuildTest.NestedPrivateClass>(true);
      InternalClassTestHelper<SmugTest.BuildTest.NestedPublicClass>(false);
      InternalClassTestHelper<SmugTest.BuildTest.NestedProtectedClass>(true);
    }

    private void InternalClassTestHelper<T>(bool fails)
    {
      try
      {
        Create.Proxy<T>();

        if (fails)
          Assert.Fail(string.Format("{0} should have failed due to being internal", typeof(T).Name));
      }
      catch (Exception ex)
      {
        if (fails && (ex is SmugCannotProxyNonPublicTypesException))
          return;

        Assert.Fail(string.Format("{0} threw: {1}", typeof(T).Name, ex.Message));
      }
    }

    public class InternalCtorClass { internal InternalCtorClass() { } }
    public class InternalCtorClass2 { internal InternalCtorClass2(int i) { } public InternalCtorClass2() { } }
    public class ProtectedInternalCtorClass { protected internal ProtectedInternalCtorClass() { } }
    public class PublicCtorClass { public PublicCtorClass() { } }
    public class ProtectedCtorClass { protected ProtectedCtorClass() { } }
    public class PrivateCtorClass { private PrivateCtorClass() { } }
    public class StaticCtorClass { static StaticCtorClass() { } }

    [TestMethod]
    public void InternalCtorTest()
    {
      InternalCtorTestHelper<InternalCtorClass>(0);
      InternalCtorTestHelper<InternalCtorClass2>(1);
      InternalCtorTestHelper<ProtectedInternalCtorClass>(1);
      InternalCtorTestHelper<PublicCtorClass>(1);
      InternalCtorTestHelper<ProtectedCtorClass>(1);
      InternalCtorTestHelper<PrivateCtorClass>(0);
      InternalCtorTestHelper<StaticCtorClass>(1);
    }

    private void InternalCtorTestHelper<T>(int expectedCtorCount)
    {
      var builder = new SourceBuilder(typeof(T));
      int ctorCount = builder.MemberCollection.Constructors.Count();
      Assert.AreEqual(expectedCtorCount, ctorCount, string.Format("{0} ctor count", typeof(T).Name));

      try
      {
        Create.Proxy<T>();
      }
      catch (Exception ex)
      {
        if (expectedCtorCount == 0 && ex is SmugNoConstructorsException)
          return;

        Assert.Fail("{0} threw exception: {1}", typeof(T).Name, ex.Message);
      }

      if (expectedCtorCount == 0)
        Assert.Fail("{0} was expected to fail for having no [public] ctors", typeof(T).Name);
    }

    public interface Iface0 { event Func<int> Foo; }
    public interface Iface1 { event Func<string> Foo; }
    public interface Iface2 { event Func<char> Foo; }

    public interface Iface3 { int Foo { get; set; } }
    public interface Iface4 { string Foo { get; set; } }
    public interface Iface5 { char Foo { get; set; } }

    public interface Iface6 { int Foo(); }
    public interface Iface7 { string Foo(); }
    public interface Iface8 { char Foo(); }

    public interface IfaceAll : Iface0, Iface1, Iface2, Iface3, Iface4, Iface5, Iface6, Iface7, Iface8 { }
    public class ExplicitClassTest : Iface0, Iface1, Iface2, Iface3, Iface4, Iface5, Iface6, Iface7, Iface8
    {
      event Func<int> Iface0.Foo { add { throw new NotImplementedException(); } remove { throw new NotImplementedException(); } }
      event Func<string> Iface1.Foo { add { throw new NotImplementedException(); } remove { throw new NotImplementedException(); } }
      event Func<char> Iface2.Foo { add { throw new NotImplementedException(); } remove { throw new NotImplementedException(); } }
      int Iface3.Foo { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }
      string Iface4.Foo { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }
      char Iface5.Foo { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }
      int Iface6.Foo() { throw new NotImplementedException(); }
      string Iface7.Foo() { throw new NotImplementedException(); }

      //iface8
      public virtual char Foo() { return 'c'; }
    }

    public class Proxy : ExplicitClassTest, Iface6
    {
      public override char Foo()
      {
        return base.Foo();
      }

      int Iface6.Foo() { return 1; }
    }

    public interface I { int Foo(); void Bar(); int Baz { get; set; } event Action MEvent; }
    public class C : I
    {
      int I.Foo() { return 1; }
      void I.Bar() { }
      int I.Baz { get { return 10; } set { } }

      public Action MEvent;
      event Action I.MEvent { add { MEvent += value; } remove { MEvent -= value; } }
    }
    public class D : C { } //not allowed to override I.Foo
    public class E : C, I
    {
      int I.Foo() { throw new NotImplementedException(); }
      void I.Bar() { throw new NotImplementedException(); }
      int I.Baz { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }
      event Action I.MEvent { add { throw new NotImplementedException(); } remove { throw new NotImplementedException(); } }
    }

    [TestMethod]
    public void ConflictingMethodsDetectingTest()
    {
      var members = new SourceBuilder(typeof(IfaceAll)).MemberCollection;
      Assert.AreEqual(10, members.Count);
      Assert.AreEqual(1, members.Constructors.Count(), "constructors");
      Assert.AreEqual(1, members.Count(m => !m.IsExplicit && m.MemberType != MemberTypes.Constructor), "non explicits");
      Assert.AreEqual(8, members.Count(m => m.IsExplicit), "explicits");

      Assert.AreEqual(typeof(Iface0), members["Foo", false].DeclaringType, "Iface0 Foo");
    }

    [TestMethod]
    public void ConflictingMethodsTest()
    {
      var conflictProxy = Create.Proxy<IfaceAll>();
      var p = Create.Proxy<C>();
      var exp = Create.Proxy<ExplicitClassTest>();
#if DETOUR_ENABLED
      C e = new E();

      var foo = InvocationHandler.GetExplicitMethod(() => ((I)null).Foo(), e, new object[] { });
      var result = foo();

      var bar = InvocationHandler.GetExplicitMethod(() => ((I)null).Bar(), e, new object[] { });
      bar();

      var baz = InvocationHandler.GetExplicitProperty(typeof(I), "Baz", e, new object[] { }, typeof(int), false);
      var zr = (int)baz();

      var bazSet = InvocationHandler.GetExplicitProperty(typeof(I), "Baz", e, new object[] { 4 }, typeof(int), true);
      bazSet();

      bool fire = false;
      Action subscrib = () => fire = true;

      var adder = InvocationHandler.GetExplicitEventAccess(typeof(I), "MEvent", e, true, subscrib);
      adder();
      e.MEvent();

      var remover = InvocationHandler.GetExplicitEventAccess(typeof(I), "MEvent", e, false, subscrib);
      remover();
      Assert.IsNull(e.MEvent);


      //Prepare.Event


      //Prepare.Func<int>(((I)p).Foo).Returns(3);
      //Assert.AreEqual(3, ((I)p).Foo());


      Prepare.Func<char>(exp.Foo).Real();
      Assert.AreEqual('c', exp.Foo());
#endif
    }

    [TestMethod]
    public void IndividualBuildTest()
    {
      var proxy = Create.Proxy<global::System.ComponentModel.IBindingList>();
    }

    [TestMethod]
    public void BasicBuildTest()
    {
      return;
      string logFile = @"C:\tmp\proxy-output.txt";

      if (File.Exists(logFile))
        File.Delete(logFile);

      var assemblies = LoadAllIn(@"c:\program files (x86)\reference assemblies\microsoft\framework\.netframework\v4.0\");
      assemblies.AddRange(new Assembly[]
      {
        typeof(FileStyleUriParser).Assembly,
        typeof(Action).Assembly
      }.Where(asm => !assemblies.Contains(asm)));

      BuildAllFrom(assemblies, msg => Log(msg, logFile));
    }

    private void Log(string msg, string destination)
    {
      var fs = new FileStream(destination, FileMode.Append, FileAccess.Write);

      var sw = new StreamWriter(fs);

      sw.WriteLine(msg);

      sw.Flush();

      fs.Dispose();
    }

    private List<Assembly> LoadAllIn(string path)
    {
      return new List<Assembly>();
    }

    private void BuildAllFrom(IEnumerable<Assembly> asms, Action<string> log)
    {
      var smugExceptions = new Dictionary<Type, int>();
      int unexpectedExceptions = 0;

      foreach (var asm in asms)
      {
        foreach (var type in asm.GetTypes())
        {
          try
          {
            var config = new ProxyConfiguration
            {
              UseRealMethodsInConstructor = true,

            };

            if (type == typeof(System.Security.Policy.EvidenceBase))
            {
              config.ClassAttributes.Add(new SerializableAttribute());
            }

            var inst = ProxyRepository.CreateInstance(type, config);
          }
          catch (SmugException sx)
          {
            if (sx is SmugCompilationException)
            {
              log(string.Format("{0} failed to compiled:\r\n{1}\r\nSource:\r\n{2}", type, DumpException(sx), ((SmugCompilationException)sx).SourceCode));
            }

            var sxt = sx.GetType();
            if (!smugExceptions.ContainsKey(sxt))
            {
              smugExceptions[sxt] = 0;
            }

            smugExceptions[sxt]++;
          }
          catch (Exception ex)
          {
            unexpectedExceptions++;
            log(string.Format("{0} failed, message: {1}", type, DumpException(ex)));
          }
        }
      }

      log(string.Format("Non smug exceptions: {0}", unexpectedExceptions));

      foreach (var sxt in smugExceptions.Keys)
      {
        log(string.Format("Smug exception [type {0}]: {1}", sxt, smugExceptions[sxt]));
      }
    }

    private string DumpException(Exception ex)
    {
      if (ex == null)
        return string.Empty;

      return
        string.Format("message:\r\n{0}\r\nstack:\r\n{1}\r\n-------------------\r\n{2}", ex.Message, ex.StackTrace, DumpException(ex.InnerException));
    }

    public interface I_NewSourceBuilderTestCase1
    {
      void Foo();
    }

    public class NewSourceBuilderTestCase1 : I_NewSourceBuilderTestCase1
    {
      public virtual void Foo() { }
      internal virtual void Bar() { }
    }

    [TestMethod]
    public void NewSourceBuilderTestCase1Test()
    {
      var inf = Create.Proxy<I_NewSourceBuilderTestCase1>();
      var cls = Create.Proxy<NewSourceBuilderTestCase1>();
    }

    public class TemplateNaming<T>
    {
      public class Nested<V>
      {
        public virtual void Foo<W>(T t, V v, W w) { }
      }
    }

    [TestMethod]
    public void TemplateNamingTest()
    {
      Create.Proxy<TemplateNaming<int>.Nested<double>>();
    }

    public interface I_Indexers
    {
      int this[int i] { get; set; }
      string this[string s] { get; set; }
      double this[int i, int j] { get; set; }
    }

    public abstract class Indexers : I_Indexers
    {
      int I_Indexers.this[int i] { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }
      public virtual char this[int i] { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }

      string I_Indexers.this[string s] { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }
      public abstract decimal this[string i] { get; set; }

      double I_Indexers.this[int i, int j] { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }
      protected abstract char this[int i, int j] { get; set; }
    }

    class Foo
    {
      //void Bar() { }
      //int Bar { get { throw new NotImplementedException(); } }
      //event Action Bar;
      int Bar { set { } }

      void Baz(int i) { }

      void Baz(long l) { }

      //public int Item { get { throw new NotImplementedException(); } set { } }

      //[System.Runtime.CompilerServices.IndexerName("MyItem")]
      //public int this[int i] { get { throw new NotImplementedException(); } set { } }
    }


    interface I_Foo
    {
      int Item { get; set; }
    }

    interface I_Bar : I_Foo
    {
      double this[string s] { get; }
    }

    class FooBar : I_Bar
    {
      double I_Bar.this[string s]
      {
        get { throw new NotImplementedException(); }
      }

      public int Item
      {
        get
        {
          throw new NotImplementedException();
        }
        set
        {
          throw new NotImplementedException();
        }
      }
    }

    [TestMethod]
    public void IndexerTest()
    {
      //Create.Proxy<I_Indexers>();
      //Create.Proxy<Indexers>();
    }

    class PropertyAccessorTestClass
    {
      public virtual int Baz { set { throw new NotImplementedException(); } }
      public virtual int get_Buz() { return 0; }
      public virtual int set_Baz() { return 1; }
    }

    [TestMethod]
    public void PropertyAccessorTestClassTest()
    {
      var members = new SourceBuilder(typeof(PropertyAccessorTestClass)).MemberCollection;
      Assert.AreEqual(1, members.Properties.Count());
      Assert.AreEqual(2, members.Methods.Count());
    }

    class NonTargetTypeVirtuals_0
    {
      public virtual void Foo() { }
    }

    class NonTargetTypeVirtuals_1 : NonTargetTypeVirtuals_0
    {
    }

    [TestMethod]
    public void NonTargetTypeVirtualsTest()
    {
      var members = new SourceBuilder(typeof(NonTargetTypeVirtuals_0)).MemberCollection;
      Assert.IsTrue(members.Contains(mex => mex.Name == "Foo"), "parent member");

      members = new SourceBuilder(typeof(NonTargetTypeVirtuals_1)).MemberCollection;
      Assert.IsTrue(members.Contains(mex => mex.Name == "Foo"), "child member");
    }
  }
}
